package tsproxy

import (
	"errors"
	"fmt"
	"strings"

	"github.com/influxdata/influxql"
)

type Rewrite struct {
	RewriteMoQueryExpr func(query *MoQueryParams) (influxql.Expr, error)
}

func (rw *Rewrite) Rewrite(expr influxql.Expr) (influxql.Expr, error) {
	// 将 sql 中 where 部分以 and 或 or 分割成数组
	exprList, isOr, err := toInfluxExprArray(expr)
	if err != nil {
		return nil, err
	}

	// 删除一些括号， 如 
	// 1. (a = 1) and b = 2, 改成 a = 1 and b = 2
	// 2. (a = 1 and b = 2) and c = 3, 改成 a = 1 and b = 2 and c = 3
	exprList, err = simpleInfluxParenExpr(exprList, isOr)
	if err != nil {
		return nil, err
	}

	// 从 expr 数组中找到字段名以  mo_ 开头的表达式将
	// 它添加到 MoQueryParams 结构中，并从 数组 删除
	// 这个表达式，并返回这个新数组
	var query MoQueryParams
	exprList, err = convertMoQuery(exprList, isOr, &query)
	if err != nil {
		return nil, err
	}

	if query.IsZero("") {
		return expr, nil
	}
  
  // 以 MoQueryParams 作为条件查询后，将结果转为 
  //    managed_object_id in (1,2,3,4,....)
  // 其中 in 后的值列表，为查询结果中 mo 的 id 列表
	moExpr, err := rw.RewriteMoQueryExpr(&query)
	if err != nil {
		return nil, err
	}

	token := influxql.AND
	if isOr {
		token = influxql.OR
	}
	expr = moExpr
	for _, e := range exprList {
		expr = &influxql.BinaryExpr{
			Op:  token,
			LHS: expr,
			RHS: e,
		}
	}
	return expr, nil
}

func toInExpr(field string, ss []string) influxql.Expr {
	return &influxql.BinaryExpr{
		Op: influxql.IN,
		LHS: &influxql.VarRef{
			Val: field,
		},
		RHS: &influxql.ParenExpr{
			Expr: &influxql.ListLiteral{
				Vals: ss,
			},
		},
	}
}

func convertMoQuery(exprList []influxql.Expr, isOr bool, query *MoQueryParams) ([]influxql.Expr, error) {
	var remain []influxql.Expr
	for _, expr := range exprList {
		bexpr, ok := expr.(*influxql.BinaryExpr)
		if !ok {
			remain = append(remain, expr)
			continue
		}

		var value influxql.Expr
		var name string

		if varref, ok := bexpr.LHS.(*influxql.VarRef); ok {
			if varref.Type != influxql.Unknown &&
				varref.Type != influxql.AnyField &&
				varref.Type != influxql.Tag {
				remain = append(remain, expr)
				continue
			}

			value = bexpr.RHS
			name = varref.Val

		} else if varref, ok = bexpr.RHS.(*influxql.VarRef); ok {
			if varref.Type != influxql.Unknown &&
				varref.Type != influxql.AnyField &&
				varref.Type != influxql.Tag {
				remain = append(remain, expr)
				continue
			}

			value = bexpr.LHS
			name = varref.Val
		} else {
			remain = append(remain, expr)
			continue
		}

		err := convertExprToMoQuery(bexpr.Op, name, value, isOr, query)
		if err != nil {
			if err == errNotMoFieldExpr {
				remain = append(remain, expr)
				continue
			}
			return nil, err
		}
	}

	return remain, nil
}

var errNotMoFieldExpr = errors.New("not mo field")

func convertExprToMoQuery(op influxql.Token, name string, value influxql.Expr, isOr bool, query *MoQueryParams) error {
	switch strings.ToLower(name) {
	case "mo_type":
		if isOr {
			if query.IsZero(name) {
				return errors.New("字段 '" + name + "' 的值不能和其它字段同进出现在 or 表达式中")
			}
		} else {
			if len(query.ByTypes) > 0 {
				return errors.New("字段 '" + name + "' 在 and 表达式中出现多次")
			}
		}

		switch op {
		case influxql.EQ:
			s, err := readStringValue(value)
			if err != nil {
				return err
			}
			query.ByTypes = append(query.ByTypes, s)
			return nil
		case influxql.IN:
			list, err := readStringList(value)
			if err != nil {
				return err
			}
			query.ByTypes = append(query.ByTypes, list...)
			return nil
		default:
			return errors.New("字段 '" + name + "' 不支持 '" + op.String() + "' 操作符")
		}
	case "mo_address":
		if isOr {
			if query.IsZero(name) {
				return errors.New("字段 '" + name + "' 的值不能和其它字段同进出现在 or 表达式中")
			}
		} else {
			if len(query.ByAddresses) > 0 {
				return errors.New("字段 '" + name + "' 在 and 表达式中出现多次")
			}
		}

		switch op {
		case influxql.EQ:
			s, err := readStringValue(value)
			if err != nil {
				return err
			}
			query.ByAddresses = append(query.ByAddresses, s)
			return nil
		case influxql.IN:
			list, err := readStringList(value)
			if err != nil {
				return err
			}
			query.ByAddresses = append(query.ByAddresses, list...)
			return nil
		default:
			return errors.New("字段 '" + name + "' 不支持 '" + op.String() + "' 操作符")
		}
	case "mo_tag":
		if isOr {
			if query.IsZero(name) {
				return errors.New("字段 '" + name + "' 的值不能和其它字段同进出现在 or 表达式中")
			}
		} else {
			if len(query.ByTags) > 0 {
				return errors.New("字段 '" + name + "' 在 and 表达式中出现多次")
			}
		}

		switch op {
		case influxql.EQ:
			s, err := readStringValue(value)
			if err != nil {
				return err
			}
			query.ByTags = append(query.ByTags, s)
			return nil
		case influxql.IN:
			list, err := readStringList(value)
			if err != nil {
				return err
			}
			query.ByTags = append(query.ByTags, list...)
			return nil
		default:
			return errors.New("字段 '" + name + "' 不支持 '" + op.String() + "' 操作符")
		}
	case "mo_domain_name":
		if isOr {
			if query.IsZero(name) {
				return errors.New("字段 '" + name + "' 的值不能和其它字段同进出现在 or 表达式中")
			}
		} else {
			if query.ByDomainName != "" {
				return errors.New("字段 '" + name + "' 在 and 表达式中出现多次")
			}
		}

		switch op {
		case influxql.EQ:
			s, err := readStringValue(value)
			if err != nil {
				return err
			}
			if s == "default" || s == "缺省" {
				query.ByDefaultDomain = true
			} else {
				query.ByDomainName = s
			}
			return nil
		// case influxql.IN:
		// 	list, err := convertExpr(true, name, value, isOr)
		// 	if err != nil {
		// 		return err
		// 	}
		// 	query.ByType = append(query.ByType, list...)
		default:
			return errors.New("字段 '" + name + "' 不支持 '" + op.String() + "' 操作符")
		}
	default:
		if strings.HasPrefix(name, "mo_") {
			return errors.New("字段 '" + name + "' 不支持")
		}
		return errNotMoFieldExpr
	}
}

func simpleInfluxParenExpr(exprList []influxql.Expr, isOr bool) ([]influxql.Expr, error) {
	idx := 0
	for idx < len(exprList) {
		expr := exprList[idx]
		if e, ok := expr.(*influxql.ParenExpr); ok {
			array, bo, err := toInfluxExprArray(e.Expr)
			if err != nil {
				idx++
				continue
			}
			if len(array) == 1 {
				exprList[idx] = array[0]
				idx++
				continue
			}
			if bo != isOr {
				idx++
				continue
			}

			tmp := make([]influxql.Expr, len(exprList)+len(array)-1)
			copy(tmp, exprList[:idx])
			copy(tmp[idx:], array)
			copy(tmp[idx+len(array):], exprList[idx+1:])
			exprList = tmp
			idx = idx + len(array)
			continue
		}
		idx++
	}
	return exprList, nil
}

func toInfluxExprArray(expr influxql.Expr) ([]influxql.Expr, bool, error) {
	var results []influxql.Expr
	var isOr = false
	for expr != nil {
		switch e := expr.(type) {
		case *influxql.BinaryExpr:
			if e.Op == influxql.AND {
				results = append(results, e.RHS)
				expr = e.LHS
				if isOr {
					return nil, false, errors.New("and 和 or 同进出现")
				}
			} else if e.Op == influxql.OR {
				if !isOr && len(results) > 0 {
					return nil, false, errors.New("and 和 or 同进出现")
				}
				results = append(results, e.RHS)
				expr = e.LHS
				isOr = true
			} else {
				results = append(results, expr)
				expr = nil
			}
		default:
			results = append(results, expr)
			expr = nil
		}
	}

	if len(results) == 1 {
		if e, ok := results[0].(*influxql.ParenExpr); ok {
			return toInfluxExprArray(e.Expr)
		}
	}
	for i := 0; i < len(results)/2; i++ {
		a := results[i]
		results[i] = results[len(results)-i-1]
		results[len(results)-i-1] = a
	}
	return results, isOr, nil
}

func readStringValue(value influxql.Expr) (string, error) {
	switch expr := value.(type) {
	case *influxql.StringLiteral:
		return expr.Val, nil
	}

	return "", fmt.Errorf("不能将 %T 转换为字符串", value)
}

func readStringList(value influxql.Expr) ([]string, error) {
	switch expr := value.(type) {
	case *influxql.ParenExpr:
		return readStringList(value)
	case *influxql.ListLiteral:
		return expr.Vals, nil
	case *influxql.StringLiteral:
		return []string{
			expr.Val,
		}, nil
	}
	return nil, fmt.Errorf("不能将 %T 转换为字符串", value)
}

// func rewriteInfluxExpr(expr influxql.Expr) (influxql.Expr, error) {
// 	switch e := expr.(type) {
// 	case *influxql.BinaryExpr:
// 		return rewriteBinaryExpr(e)

// 	case *influxql.BooleanLiteral:
// 		return expr, nil

// 	case *influxql.BoundParameter:
// 		return expr, nil

// 	case *influxql.Call:
// 		return expr, nil

// 	case *influxql.Distinct:
// 		return expr, nil

// 	case *influxql.DurationLiteral:
// 		return expr, nil

// 	case *influxql.IntegerLiteral:
// 		return expr, nil

// 	case *influxql.UnsignedLiteral:
// 		return expr, nil

// 	case *influxql.NilLiteral:
// 		return expr, nil

// 	case *influxql.NumberLiteral:
// 		return expr, nil

// 	case *influxql.ParenExpr:
// 		return rewriteParenExpr(e)

// 	case *influxql.RegexLiteral:
// 		return expr, nil

// 	case *influxql.ListLiteral:
// 		return expr, nil

// 	case *influxql.StringLiteral:
// 		return expr, nil

// 	case *influxql.TimeLiteral:
// 		return expr, nil

// 	case *influxql.VarRef:
// 		return expr, nil

// 	case *influxql.Wildcard:
// 		return expr, nil
// 	}
// 	return expr, nil
// }

// func rewriteParenExpr(expr *influxql.ParenExpr) (influxql.Expr, error) {
// 	return expr, nil
// }

// func rewriteBinaryExpr(expr *influxql.BinaryExpr) (influxql.Expr, error) {
// 	// var isLHS = true
// 	// varref, ok := expr.LHS.(*influxql.VarRef)
// 	// if ok {
// 	// 	if !strings.HasPrefix(varref.Val, "mo_") {
// 	// 		return expr
// 	// 	}
// 	// 	isLHS = true
// 	// } else {
// 	// 	varref, ok = expr.RHS.(*influxql.VarRef)
// 	// 	if !ok {
// 	// 		return expr
// 	// 	}

// 	// 	if !strings.HasPrefix(varref.Val, "mo_") {
// 	// 		return expr
// 	// 	}

// 	// 	isLHS = false
// 	// }

// 	// values
// 	// switch expr.Op {
// 	// case "=", "==":

// 	// }
// 	return expr, nil
// }
